#include "Character.h"
#include "Level.h"
#include "Camera.h"

Animation::Animation(int count, int left, int top, int right, int bottom) {
    this->count  = count;
    this->left   = left;
    this->top    = top;
    this->right  = right;
    this->bottom = bottom;
}

Animation::Animation(TiXmlElement* frameset) {    
    count  = Chameleon(frameset->Attribute("count"));
    left   = Chameleon(frameset->Attribute("left"));
    top    = Chameleon(frameset->Attribute("top"));
    right  = Chameleon(frameset->Attribute("right"));
    bottom = Chameleon(frameset->Attribute("bottom"));
}

Animated::Animated(string name) {
    path = CHARACTERS_DIR + name + "/";
    string filename(CHARACTER_XML);
    
    // Retrieve the characters xml
    xmlDoc = new TiXmlDocument((path + filename).c_str());
    if (!xmlDoc->LoadFile()) throw FILE_NOT_FOUND;
    xml = xmlDoc->RootElement();
    
    busy   = 0;
    sprite = *new Sprite(imageManager.getImage(xml->Attribute("spritesheet"), path));
}

Animated::~Animated() {
    delete xmlDoc;
}

void Animated::animate(Animation &animation) {
    animationQueue.clear();
    
    int spriteWidth = ((animation.right - animation.left) / animation.count);
    
    for (int i = 0; i < animation.count; i++) {
        Rect<int> subRect;
        
        subRect.Left   = animation.left;
        subRect.Top    = animation.top;
        subRect.Right  = animation.left + spriteWidth;
        subRect.Bottom = animation.bottom;
        animationQueue.push_back(subRect);
        
        animation.left += spriteWidth;
    }
}

void Animated::animate(TiXmlElement* frameset) {
    Animation animation(frameset);
    animate(animation);
}

Sprite& Animated::getSprite() {
    if ((clock.GetElapsedTime() * 100) > (100 / FRAMES_PER_SECOND)) {
        clock.Reset();

        if (animationQueue.empty()) {
            TiXmlElement* idleXml = getChildElementbyName("idle", xml->FirstChildElement("movements"));
            
            // Set idle animation
            animate(idleXml->FirstChildElement("frameset"));
        }
        
        sprite.SetSubRect(animationQueue.front());
        animationQueue.pop_front();

        if (busy > 0) --busy;
    }
    
    return sprite;
}

Character::Character(string name, Vector2f& defaultPosition, Level* level) : Animated(name) {
    this->level     = level;
    rank            = xml->Attribute("rank");
    this->name      = xml->Attribute("name");
    maxHealth       = Chameleon(xml->Attribute("health"));
    currentHealth   = maxHealth;
    this->defaultPosition = defaultPosition;
    x               = 0;
    y               = 0;
    orientation     = RIGHT;
}

bool Character::isAlive() {
    if (currentHealth > 0) return true;
    else return false;
}

bool Character::isBusy() {
    if (busy > 0) return true;
    else return false;
}

void Character::walk(Direction direction) {
    if (!busy) {
        TiXmlElement* walkXml = getChildElementbyName("walk", xml->FirstChildElement("movements"));
        
        // Don't prematurely reset walking animation
        if (animationQueue.empty()) {
            animate(walkXml->FirstChildElement("frameset"));
        }
        
        int distance = Chameleon(walkXml->Attribute("speed"));
        
        // Make the character take a "step" if possible
        if (direction == LEFT && x > 0) {
            distance = distance * -1;
            x += distance;
            sprite.Move(distance, 0);
            
            sprite.FlipX(true);
            orientation = LEFT;
        } else if (direction == RIGHT && x < level->getWidth()) {
            x += distance;
            sprite.Move(distance, 0);
            
            sprite.FlipX(false);
            orientation = RIGHT;
        }
    }
}

void Character::jump() {
    if (!busy) {
        TiXmlElement* jumpXml = getChildElementbyName("jump", xml->FirstChildElement("movements"));

        animate(jumpXml->FirstChildElement("frameset"));
        
        // TODO: jump physics
    }
}

void Character::attack(string name) {
    if (!busy) {
        TiXmlElement* attackXml = getChildElementbyName(name, xml->FirstChildElement("attacks"));
        
        // Handle animation
        animate(attackXml->FirstChildElement("frameset"));
        
        // Deal damage (if applicable)
        int damage = Chameleon(attackXml->Attribute("damage"));
        
        for (vector<Character*>::iterator i = level->characters.begin();
             i != level->characters.end();
             i++) {
            if (getDistanceFrom(*i) <= getSprite().GetSize().x && getDistanceFrom(*i) >= 0 && this != (*i)) {
                (*i)->hit(damage);
            }
        }
        
        // TODO: can't make busy so character can still move but can only attack once...
        busy = Chameleon(attackXml->FirstChildElement("frameset")->Attribute("count"));
    }
}

void Character::hit(int damage) {
    TiXmlElement* hitXml = getChildElementbyName("hit", xml->FirstChildElement("movements"));

    // Handle animation
    TiXmlElement* frameset  = hitXml->FirstChildElement("frameset");
    Animation animation(frameset);
    animate(animation);
    
    // Deal damage
    currentHealth -= damage;
    
    busy = animation.count;
}

int Character::getDistanceFrom(Character* enemy) {
    int distance;
    
    if (getX() < enemy->getX()) {           // On the left        
        distance = enemy->getX() - getX();
        
        if (orientation == LEFT) distance *= -1;
    } else if (getX() > enemy->getX()) {    // On the right
        distance = getX() - enemy->getX();
        
        if (orientation == RIGHT) distance *= -1;
    } else {                                // Occupy same spot
        distance = 0;
    }
    
    return distance;
}

string    Character::getRank()            { return rank; }
string    Character::getName()            { return name; }
int       Character::getMaxHealth()       { return maxHealth;}
int       Character::getHealth()          { return currentHealth; }
Vector2f& Character::getDefaultPosition() { return defaultPosition; }
int       Character::getX()               { return x; }
int       Character::getY()               { return y; }
Direction Character::getOrientation()     { return orientation; }

void Character::setX(int x) {
    sprite.SetX(x);
    this->x = x;
}

void Character::setY(int y) {
    sprite.SetY(y);
    this->y = y;
}

void Hero::walk(Direction direction) {
    if (!busy) {
        TiXmlElement* walkXml = getChildElementbyName("walk", xml->FirstChildElement("movements"));
        
        // Don't prematurely reset walking animation
        if (animationQueue.empty()) {
            animate(walkXml->FirstChildElement("frameset"));
        }
        
        int distance = Chameleon(walkXml->Attribute("speed"));
        
        // Make the character take a "step" if possible
        if (direction == LEFT && x > 0) {
            distance = distance * -1;
            x       += distance;
            
            sprite.FlipX(true);
            orientation = LEFT;
            
            // If camera is already at its boundary, move the sprite
            if (x <= defaultPosition.x ||
                x >= level->getWidth() - (WINDOW_WIDTH - defaultPosition.x)) {
                sprite.Move(distance, 0);
            } else { // Else just move the camera
                level->camera->move(distance, 0);
            }
        } else if (direction == RIGHT && x < level->getWidth()) {
            x += distance;
            
            sprite.FlipX(false);
            orientation = RIGHT;
            
            if (x >= level->getWidth() - (WINDOW_WIDTH - defaultPosition.x) ||
                x <= defaultPosition.x) {
                sprite.Move(distance, 0);
            } else {
                level->camera->move(distance, 0);
            }
        }
    }
}
